/**
 * Copyright 2019 吉鼎科技.

 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.easyplatform.support.scripting.cmd;

import cn.easyplatform.EasyPlatformWithLabelKeyException;
import cn.easyplatform.contexts.WorkflowContext;
import cn.easyplatform.dao.IdentityDao;
import cn.easyplatform.dos.FieldDo;
import cn.easyplatform.dos.UserDo;
import cn.easyplatform.entities.beans.task.TaskBean;
import cn.easyplatform.interceptor.CommandContext;
import cn.easyplatform.lang.Files;
import cn.easyplatform.lang.Lang;
import cn.easyplatform.lang.Nums;
import cn.easyplatform.lang.Strings;
import cn.easyplatform.shiro.PasswordEncoder;
import cn.easyplatform.support.scripting.AbstractScriptCmd;
import cn.easyplatform.support.transform.TagNode;
import cn.easyplatform.support.transform.Transformer;
import cn.easyplatform.support.transform.TransformerFactory;
import cn.easyplatform.support.transform.handler.StructuringHandler;
import cn.easyplatform.type.DeviceType;
import cn.easyplatform.type.FieldType;
import cn.easyplatform.util.ConfigTools;
import cn.easyplatform.util.RuntimeUtils;
import cn.easyplatform.utils.HttpUtil;
import cn.easyplatform.utils.SerializationUtils;
import com.alibaba.fastjson.JSON;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.AuthenticatingRealm;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.util.*;
import java.util.regex.Pattern;

/**
 * @author <a href="mailto:davidchen@epclouds.com">littleDog</a> <br/>
 * @since 2.0.0 <br/>
 */
public class UtilCmd extends AbstractScriptCmd {

    private static final int DEF_DIV_SCALE = 2;

    UtilCmd(MainCmd cr) {
        super(cr);
    }

    public void fromXml(String content) {
        Document doc = null;
        try {
            doc = DocumentHelper.parseText(content);
        } catch (DocumentException e) {
            throw Lang.wrapThrow(e);
        }
        TaskBean tb = new TaskBean();
        tb.setId("fromXml");
        tb.setName(tb.getId());
        WorkflowContext ctx = new WorkflowContext(tb, 0, scc.getTarget());
        scc.getCommandContext().setWorkflowContext(ctx);
        try {
            Transformer<Document, Map<String, List<TagNode>>> trans = TransformerFactory
                    .createTransformer(Transformer.XML2TABLE);
            trans.setHandler(new StructuringHandler(scc.getCommandContext(),
                    ctx.getRecord(), "XML"));
            trans.transform(doc, null);
        } finally {
            scc.getCommandContext().removeWorkflowContext();
            scc.getCommandContext().setWorkflowContext(
                    scc.getWorkflowContext());
        }
    }

    public void fromSwift(String content) {
        TaskBean tb = new TaskBean();
        tb.setId("fromSwift");
        tb.setName(tb.getId());
        WorkflowContext ctx = new WorkflowContext(tb, 0, scc.getTarget());
        scc.getCommandContext().setWorkflowContext(ctx);
        try {
            Transformer<String, Map<String, List<TagNode>>> trans = TransformerFactory
                    .createTransformer(Transformer.SWI2TABLE);
            trans.setHandler(new StructuringHandler(scc.getCommandContext(),
                    ctx.getRecord(), "SWI"));
            trans.transform(content, null);
        } finally {
            scc.getCommandContext().removeWorkflowContext();
            scc.getCommandContext().setWorkflowContext(
                    scc.getWorkflowContext());
        }
    }

    @SuppressWarnings("rawtypes")
    public Object fromJson(String content) {
        try {
            return JSON.parse(content);
        } catch (Exception e) {
            throw Lang.wrapThrow(e);
        }
    }

    public String toJson(Object obj) {
        if (obj == null)
            return "";
        if (obj instanceof FieldDo[]) {
            Map<String, Object> map = new HashMap<String, Object>();
            FieldDo[] args = (FieldDo[]) obj;
            for (FieldDo field : args)
                map.put(field.getName(), field.getValue());
            obj = map;
        }
        try {
            return JSON.toJSONString(obj);
        } catch (Exception e) {
            throw Lang.wrapThrow(e);
        }
    }

    /**
     * 根据模版生成新的数据，模版内容有#{xx}用栏位xx来代替
     *
     * @param template
     * @return
     */
    public String replace(String template) {
        if (template == null)
            throw new IllegalArgumentException(
                    "replace:template must not be null");
        char[] cs = template.toCharArray();
        int len = cs.length;
        StringBuilder sb = new StringBuilder(len * 3);
        StringBuilder tmp = new StringBuilder();
        for (int i = 0; i < len; i++) {
            if (cs[i] == '#' && i < len - 1 && cs[i + 1] == '{') {
                i += 2;
                tmp.setLength(0);
                while (i < cs.length) {
                    if (cs[i] == '}')
                        break;
                    else
                        tmp.append(cs[i]);
                    i++;
                }
                Object value = scc.getTarget().getValue(tmp.toString());
                sb.append(value == null ? "" : value);
            } else
                sb.append(cs[i]);
        }
        tmp = null;
        return sb.toString();
    }

    public String toFile(Object data, String file) {
        if (data == null)
            throw new IllegalArgumentException("toField:data must not be null");
        if (file == null)
            throw new IllegalArgumentException("toField:file must not be null");
        CommandContext cc = scc.getCommandContext();
        String format = cc.getProjectService().getConfig().getDateFormat();
        file = file.replace("&dt", DateFormatUtils.format(new Date(), format));
        try {
            Files.write(cc.getRealPath(file), data);
            return file;
        } catch (Exception e) {
            throw new EasyPlatformWithLabelKeyException(
                    "script.engine.cmd.file", file);
        }
    }

    public Object fromFile(String file) {
        return fromFile(file, 0);
    }

    public Object fromFile(String file, int type) {
        if (file == null)
            throw new IllegalArgumentException("fromFile:file must not be null");
        CommandContext cc = scc.getCommandContext();
        file = cc.getRealPath(file);
        InputStream is = null;
        try {
            is = new BufferedInputStream(new FileInputStream(new File(file)));
            if (type == 0)
                return IOUtils.toString(is);
            else
                return IOUtils.toByteArray(is);
        } catch (Exception e) {
            throw new EasyPlatformWithLabelKeyException(
                    "script.engine.cmd.file", file);
        } finally {
            IOUtils.closeQuietly(is);
        }
    }

    /**
     * 用户密码
     *
     * @param str
     * @return
     */
    public String password(String str) {
        AuthenticatingRealm realm = (AuthenticatingRealm) ((DefaultSecurityManager) SecurityUtils.getSecurityManager()).getRealms().iterator().next();
        if (realm.getCredentialsMatcher() instanceof PasswordEncoder) {
            PasswordEncoder encoder = (PasswordEncoder) realm.getCredentialsMatcher();
            return encoder.encode(str);
        }
        SimpleHash hash = new SimpleHash("SHA-256", str, null, 1024);
        return hash.toBase64();
    }

    /**
     * 加密
     *
     * @param secret
     * @return
     */
    public String encode(String secret) {
        try {
            return ConfigTools.encrypt(secret);
        } catch (Exception ex) {
            throw new EasyPlatformWithLabelKeyException("script.encode.error",
                    ex.getMessage());
        }
    }

    /**
     * 解密
     *
     * @param secret
     * @return
     */
    public String decode(String secret) {
        try {
            return ConfigTools.decrypt(secret);
        } catch (Exception ex) {
            throw new EasyPlatformWithLabelKeyException("script.decode.error",
                    ex.getMessage());
        }
    }

    public int toInt(Object o) {
        return Nums.toInt(o, 0);
    }

    public long toLong(Object o) {
        return Nums.toLong(o, 0);
    }

    public double toDouble(Object o) {
        if (o == null)
            return 0d;
        if (o instanceof Number)
            return ((Number) o).doubleValue();
        try {
            return Double.parseDouble(o.toString());
        } catch (NumberFormatException ex) {
            return 0d;
        }
    }

    public String format(Object o, String pattern) {
        if (o == null)
            return "";
        if (o instanceof Date) {
            if (!Strings.isBlank(pattern))
                return DateFormatUtils.format((Date) o, pattern);
            else
                return DateFormat
                        .getDateInstance(
                                DateFormat.MEDIUM,
                                scc.getCommandContext().getProjectService()
                                        .getLocale()).format((Date) o);
        } else if (o instanceof Number) {
            if (!Strings.isBlank(pattern)) {
                DecimalFormat dm = new DecimalFormat(pattern);
                return dm.format(o);
            } else
                return DecimalFormat
                        .getInstance(
                                scc.getCommandContext().getProjectService()
                                        .getLocale()).format(o);

        } else
            return o.toString();
    }

    public String toStr(Object o) {
        return toStr(o, "INT");
    }

    public String toStr(Object value, String tp) {
        if (value == null)
            return "";
        if (Strings.isBlank(tp))
            tp = "INT";
        FieldType type = FieldType.valueOf(tp.toUpperCase());
        switch (type) {
            case CHAR:
            case VARCHAR:
            case CLOB:
            case BOOLEAN:
            case DATETIME:
            case TIME:
            case DATE:
                return value.toString();
            case INT:
                if (value instanceof Number)
                    return ((Number) value).intValue() + "";
                else
                    return "" + Nums.toInt(value, 0);
            case LONG:
                if (value instanceof Number)
                    return ((Number) value).longValue() + "";
                else
                    return "" + Nums.toLong(value, 0);
            case NUMERIC:
                if (value instanceof Number)
                    return ((Number) value).doubleValue() + "";
                else
                    return value.toString();
            default:
                throw new EasyPlatformWithLabelKeyException("dao.access.getType",
                        "", type);
        }
    }

    /**
     * 提供精确的加法运算。
     *
     * @param v1 被加数
     * @param v2 加数
     * @return 两个参数的和
     */
    public double add(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return (b1.add(b2)).doubleValue();
    }

    /**
     * 提供精确的减法运算。
     *
     * @param v1 被减数
     * @param v2 减数
     * @return 两个参数的差
     */
    public double sub(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return (b1.subtract(b2)).doubleValue();
    }

    /**
     * 提供精确的乘法运算。
     *
     * @param v1 被乘数
     * @param v2 乘数
     * @return 两个参数的积
     */
    public double mul(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return (b1.multiply(b2)).doubleValue();
    }

    /**
     * 提供（相对）精确的除法运算，当发生除不尽的情况时，精确到 小数点以后多少位，以后的数字四舍五入。
     *
     * @param v1 被除数
     * @param v2 除数
     * @return 两个参数的商
     */
    public double div(double v1, double v2) {
        return div(v1, v2, DEF_DIV_SCALE);
    }

    /**
     * 提供（相对）精确的除法运算。当发生除不尽的情况时，由scale参数指 定精度，以后的数字四舍五入。
     *
     * @param v1    被除数
     * @param v2    除数
     * @param scale 表示表示需要精确到小数点以后几位。
     * @return 两个参数的商
     */
    public double div(double v1, double v2, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException(
                    "The scale must be a positive integer or zero");
        }
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return (b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP)).doubleValue();
    }

    /**
     * 提供精确的小数位四舍五入处理。
     *
     * @param v     需要四舍五入的数字
     * @param scale 小数点后保留几位
     * @return 四舍五入后的结果
     */
    public double round(double v, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException(
                    "The scale must be a positive integer or zero");
        }
        if (Double.isNaN(v))
            return 0;
        BigDecimal b = new BigDecimal(Double.toString(v));
        BigDecimal one = new BigDecimal("1");
        return (b.divide(one, scale, BigDecimal.ROUND_HALF_UP)).doubleValue();
    }

    // ////////////////////////////字符串操作//////////////////////////

    /**
     * <p>
     * Right pad a String with spaces (' ').
     * </p>
     * <p>
     * <p>
     * The String is padded to the size of {@code size}.
     * </p>
     * <p>
     * <pre>
     * StringUtils.rightPad(null, *)   = null
     * StringUtils.rightPad("", 3)     = "   "
     * StringUtils.rightPad("bat", 3)  = "bat"
     * StringUtils.rightPad("bat", 5)  = "bat  "
     * StringUtils.rightPad("bat", 1)  = "bat"
     * StringUtils.rightPad("bat", -1) = "bat"
     * </pre>
     *
     * @param size the size to pad to
     * @return right padded String or original String if no padding is
     * necessary, {@code null} if null String input
     */
    public String rightPad(Object o, int size) {
        if (o == null)
            return StringUtils.rightPad("", size);
        return StringUtils.rightPad(o.toString(), size);
    }

    /**
     * <p>
     * Right pad a String with a specified String.
     * </p>
     * <p>
     * <p>
     * The String is padded to the size of {@code size}.
     * </p>
     * <p>
     * <pre>
     * StringUtils.rightPad(null, *, *)      = null
     * StringUtils.rightPad("", 3, "z")      = "zzz"
     * StringUtils.rightPad("bat", 3, "yz")  = "bat"
     * StringUtils.rightPad("bat", 5, "yz")  = "batyz"
     * StringUtils.rightPad("bat", 8, "yz")  = "batyzyzy"
     * StringUtils.rightPad("bat", 1, "yz")  = "bat"
     * StringUtils.rightPad("bat", -1, "yz") = "bat"
     * StringUtils.rightPad("bat", 5, null)  = "bat  "
     * StringUtils.rightPad("bat", 5, "")    = "bat  "
     * </pre>
     *
     * @param size the size to pad to
     * @return right padded String or original String if no padding is
     * necessary, {@code null} if null String input
     */
    public String rightPad(Object o, int size, String c) {
        if (o == null)
            return StringUtils.rightPad("", size, c);
        return StringUtils.rightPad(o.toString(), size, c);
    }

    /**
     * <p>
     * Left pad a String with spaces (' ').
     * </p>
     * <p>
     * <p>
     * The String is padded to the size of {@code size}.
     * </p>
     * <p>
     * <pre>
     * StringUtils.leftPad(null, *)   = null
     * StringUtils.leftPad("", 3)     = "   "
     * StringUtils.leftPad("bat", 3)  = "bat"
     * StringUtils.leftPad("bat", 5)  = "  bat"
     * StringUtils.leftPad("bat", 1)  = "bat"
     * StringUtils.leftPad("bat", -1) = "bat"
     * </pre>
     *
     * @param size the size to pad to
     * @return left padded String or original String if no padding is necessary,
     * {@code null} if null String input
     */
    public String leftPad(Object o, int size) {
        if (o == null)
            return StringUtils.leftPad("", size);
        return StringUtils.leftPad(o.toString(), size);
    }

    /**
     * <p>
     * Left pad a String with a specified String.
     * </p>
     * <p>
     * <p>
     * Pad to a size of {@code size}.
     * </p>
     * <p>
     * <pre>
     * StringUtils.leftPad(null, *, *)      = null
     * StringUtils.leftPad("", 3, "z")      = "zzz"
     * StringUtils.leftPad("bat", 3, "yz")  = "bat"
     * StringUtils.leftPad("bat", 5, "yz")  = "yzbat"
     * StringUtils.leftPad("bat", 8, "yz")  = "yzyzybat"
     * StringUtils.leftPad("bat", 1, "yz")  = "bat"
     * StringUtils.leftPad("bat", -1, "yz") = "bat"
     * StringUtils.leftPad("bat", 5, null)  = "  bat"
     * StringUtils.leftPad("bat", 5, "")    = "  bat"
     * </pre>
     *
     * @param size the size to pad to
     * @return left padded String or original String if no padding is necessary,
     * {@code null} if null String input
     */
    public String leftPad(Object o, int size, String c) {
        if (o == null)
            return StringUtils.leftPad("", size, c);
        return StringUtils.leftPad(o.toString(), size, c);
    }

    /**
     * <p>
     * Gets a substring from the specified String avoiding exceptions.
     * </p>
     * <p>
     * <p>
     * A negative start position can be used to start {@code n} characters from
     * the end of the String.
     * </p>
     * <p>
     * <p>
     * A {@code null} String will return {@code null}. An empty ("") String will
     * return "".
     * </p>
     * <p>
     * <pre>
     * StringUtils.substring(null, *)   = null
     * StringUtils.substring("", *)     = ""
     * StringUtils.substring("abc", 0)  = "abc"
     * StringUtils.substring("abc", 2)  = "c"
     * StringUtils.substring("abc", 4)  = ""
     * StringUtils.substring("abc", -2) = "bc"
     * StringUtils.substring("abc", -4) = "abc"
     * </pre>
     *
     * @param str   the String to get the substring from, may be null
     * @param start the position to start from, negative means count back from the
     *              end of the String by this many characters
     * @return substring from start position, {@code null} if null String input
     */
    public String substr(final String str, int start) {
        return StringUtils.substring(str, start);
    }

    /**
     * <p>
     * Gets a substring from the specified String avoiding exceptions.
     * </p>
     * <p>
     * <p>
     * A negative start position can be used to start/end {@code n} characters
     * from the end of the String.
     * </p>
     * <p>
     * <p>
     * The returned substring starts with the character in the {@code start}
     * position and ends before the {@code end} position. All position counting
     * is zero-based -- i.e., to start at the beginning of the string use
     * {@code start = 0}. Negative start and end positions can be used to
     * specify offsets relative to the end of the String.
     * </p>
     * <p>
     * <p>
     * If {@code start} is not strictly to the left of {@code end}, "" is
     * returned.
     * </p>
     * <p>
     * <pre>
     * StringUtils.substring(null, *, *)    = null
     * StringUtils.substring("", * ,  *)    = "";
     * StringUtils.substring("abc", 0, 2)   = "ab"
     * StringUtils.substring("abc", 2, 0)   = ""
     * StringUtils.substring("abc", 2, 4)   = "c"
     * StringUtils.substring("abc", 4, 6)   = ""
     * StringUtils.substring("abc", 2, 2)   = ""
     * StringUtils.substring("abc", -2, -1) = "b"
     * StringUtils.substring("abc", -4, 2)  = "ab"
     * </pre>
     *
     * @param str   the String to get the substring from, may be null
     * @param start the position to start from, negative means count back from the
     *              end of the String by this many characters
     * @param end   the position to end at (exclusive), negative means count back
     *              from the end of the String by this many characters
     * @return substring from start position to end position, {@code null} if
     * null String input
     */
    public String substr(final String str, int start, int end) {
        return StringUtils.substring(str, start, end);
    }

    /**
     * <p>
     * Gets the leftmost {@code len} characters of a String.
     * </p>
     * <p>
     * <p>
     * If {@code len} characters are not available, or the String is
     * {@code null}, the String will be returned without an exception. An empty
     * String is returned if len is negative.
     * </p>
     * <p>
     * <pre>
     * StringUtils.left(null, *)    = null
     * StringUtils.left(*, -ve)     = ""
     * StringUtils.left("", *)      = ""
     * StringUtils.left("abc", 0)   = ""
     * StringUtils.left("abc", 2)   = "ab"
     * StringUtils.left("abc", 4)   = "abc"
     * </pre>
     *
     * @param str the String to get the leftmost characters from, may be null
     * @param len the length of the required String
     * @return the leftmost characters, {@code null} if null String input
     */
    public String left(final String str, final int len) {
        return StringUtils.left(str, len);
    }

    /**
     * <p>
     * Gets the rightmost {@code len} characters of a String.
     * </p>
     * <p>
     * <p>
     * If {@code len} characters are not available, or the String is
     * {@code null}, the String will be returned without an an exception. An
     * empty String is returned if len is negative.
     * </p>
     * <p>
     * <pre>
     * StringUtils.right(null, *)    = null
     * StringUtils.right(*, -ve)     = ""
     * StringUtils.right("", *)      = ""
     * StringUtils.right("abc", 0)   = ""
     * StringUtils.right("abc", 2)   = "bc"
     * StringUtils.right("abc", 4)   = "abc"
     * </pre>
     *
     * @param str the String to get the rightmost characters from, may be null
     * @param len the length of the required String
     * @return the rightmost characters, {@code null} if null String input
     */
    public String right(final String str, final int len) {
        return StringUtils.right(str, len);
    }

    /**
     * <p>
     * Gets the String that is nested in between two Strings. Only the first
     * match is returned.
     * </p>
     * <p>
     * <p>
     * A {@code null} input String returns {@code null}. A {@code null}
     * open/close returns {@code null} (no match). An empty ("") open and close
     * returns an empty string.
     * </p>
     * <p>
     * <pre>
     * StringUtils.substringBetween("wx[b]yz", "[", "]") = "b"
     * StringUtils.substringBetween(null, *, *)          = null
     * StringUtils.substringBetween(*, null, *)          = null
     * StringUtils.substringBetween(*, *, null)          = null
     * StringUtils.substringBetween("", "", "")          = ""
     * StringUtils.substringBetween("", "", "]")         = null
     * StringUtils.substringBetween("", "[", "]")        = null
     * StringUtils.substringBetween("yabcz", "", "")     = ""
     * StringUtils.substringBetween("yabcz", "y", "z")   = "abc"
     * StringUtils.substringBetween("yabczyabcz", "y", "z")   = "abc"
     * </pre>
     *
     * @param str   the String containing the substring, may be null
     * @param open  the String before the substring, may be null
     * @param close the String after the substring, may be null
     * @return the substring, {@code null} if no match
     * @since 2.0
     */
    public String substrBetween(final String str, final String open,
                                final String close) {
        return StringUtils.substringBetween(str, open, close);
    }

    /**
     * <p>
     * Gets the substring after the last occurrence of a separator. The
     * separator is not returned.
     * </p>
     * <p>
     * <p>
     * A {@code null} string input will return {@code null}. An empty ("")
     * string input will return the empty string. An empty or {@code null}
     * separator will return the empty string if the input string is not
     * {@code null}.
     * </p>
     * <p>
     * <p>
     * If nothing is found, the empty string is returned.
     * </p>
     * <p>
     * <pre>
     * StringUtils.substringAfterLast(null, *)      = null
     * StringUtils.substringAfterLast("", *)        = ""
     * StringUtils.substringAfterLast(*, "")        = ""
     * StringUtils.substringAfterLast(*, null)      = ""
     * StringUtils.substringAfterLast("abc", "a")   = "bc"
     * StringUtils.substringAfterLast("abcba", "b") = "a"
     * StringUtils.substringAfterLast("abc", "c")   = ""
     * StringUtils.substringAfterLast("a", "a")     = ""
     * StringUtils.substringAfterLast("a", "z")     = ""
     * </pre>
     *
     * @param str       the String to get a substring from, may be null
     * @param separator the String to search for, may be null
     * @return the substring after the last occurrence of the separator,
     * {@code null} if null String input
     * @since 2.0
     */
    public String substrAfterLast(final String str, final String separator) {
        return StringUtils.substringAfterLast(str, separator);
    }

    /**
     * <p>
     * Gets the substring before the first occurrence of a separator. The
     * separator is not returned.
     * </p>
     * <p>
     * <p>
     * A {@code null} string input will return {@code null}. An empty ("")
     * string input will return the empty string. A {@code null} separator will
     * return the input string.
     * </p>
     * <p>
     * <p>
     * If nothing is found, the string input is returned.
     * </p>
     * <p>
     * <pre>
     * StringUtils.substringBefore(null, *)      = null
     * StringUtils.substringBefore("", *)        = ""
     * StringUtils.substringBefore("abc", "a")   = ""
     * StringUtils.substringBefore("abcba", "b") = "a"
     * StringUtils.substringBefore("abc", "c")   = "ab"
     * StringUtils.substringBefore("abc", "d")   = "abc"
     * StringUtils.substringBefore("abc", "")    = ""
     * StringUtils.substringBefore("abc", null)  = "abc"
     * </pre>
     *
     * @param str       the String to get a substring from, may be null
     * @param separator the String to search for, may be null
     * @return the substring before the first occurrence of the separator,
     * {@code null} if null String input
     * @since 2.0
     */
    public String substrBefore(final String str, final String separator) {
        return StringUtils.substringBefore(str, separator);
    }

    /**
     * <p>
     * Gets the substring before the last occurrence of a separator. The
     * separator is not returned.
     * </p>
     * <p>
     * <p>
     * A {@code null} string input will return {@code null}. An empty ("")
     * string input will return the empty string. An empty or {@code null}
     * separator will return the input string.
     * </p>
     * <p>
     * <p>
     * If nothing is found, the string input is returned.
     * </p>
     * <p>
     * <pre>
     * StringUtils.substringBeforeLast(null, *)      = null
     * StringUtils.substringBeforeLast("", *)        = ""
     * StringUtils.substringBeforeLast("abcba", "b") = "abc"
     * StringUtils.substringBeforeLast("abc", "c")   = "ab"
     * StringUtils.substringBeforeLast("a", "a")     = ""
     * StringUtils.substringBeforeLast("a", "z")     = "a"
     * StringUtils.substringBeforeLast("a", null)    = "a"
     * StringUtils.substringBeforeLast("a", "")      = "a"
     * </pre>
     *
     * @param str       the String to get a substring from, may be null
     * @param separator the String to search for, may be null
     * @return the substring before the last occurrence of the separator,
     * {@code null} if null String input
     * @since 2.0
     */
    public String substrBeforeLast(final String str, final String separator) {
        return StringUtils.substringBeforeLast(str, separator);
    }

    /**
     * <p>
     * Gets the substring after the first occurrence of a separator. The
     * separator is not returned.
     * </p>
     * <p>
     * <p>
     * A {@code null} string input will return {@code null}. An empty ("")
     * string input will return the empty string. A {@code null} separator will
     * return the empty string if the input string is not {@code null}.
     * </p>
     * <p>
     * <p>
     * If nothing is found, the empty string is returned.
     * </p>
     * <p>
     * <pre>
     * StringUtils.substringAfter(null, *)      = null
     * StringUtils.substringAfter("", *)        = ""
     * StringUtils.substringAfter(*, null)      = ""
     * StringUtils.substringAfter("abc", "a")   = "bc"
     * StringUtils.substringAfter("abcba", "b") = "cba"
     * StringUtils.substringAfter("abc", "c")   = ""
     * StringUtils.substringAfter("abc", "d")   = ""
     * StringUtils.substringAfter("abc", "")    = "abc"
     * </pre>
     *
     * @param str       the String to get a substring from, may be null
     * @param separator the String to search for, may be null
     * @return the substring after the first occurrence of the separator,
     * {@code null} if null String input
     * @since 2.0
     */
    public String substrAfter(final String str, final String separator) {
        return StringUtils.substringAfter(str, separator);
    }

    /**
     * <p>
     * Gets the String that is nested in between two instances of the same
     * String.
     * </p>
     * <p>
     * <p>
     * A {@code null} input String returns {@code null}. A {@code null} tag
     * returns {@code null}.
     * </p>
     * <p>
     * <pre>
     * StringUtils.substringBetween(null, *)            = null
     * StringUtils.substringBetween("", "")             = ""
     * StringUtils.substringBetween("", "tag")          = null
     * StringUtils.substringBetween("tagabctag", null)  = null
     * StringUtils.substringBetween("tagabctag", "")    = ""
     * StringUtils.substringBetween("tagabctag", "tag") = "abc"
     * </pre>
     *
     * @param str the String containing the substring, may be null
     * @param tag the String before and after the substring, may be null
     * @return the substring, {@code null} if no match
     * @since 2.0
     */
    public String substrBetween(final String str, final String tag) {
        return StringUtils.substringBetween(str, tag);
    }

    /**
     * <p>
     * Searches a String for substrings delimited by a start and end tag,
     * returning all matching substrings in an array.
     * </p>
     * <p>
     * <p>
     * A {@code null} input String returns {@code null}. A {@code null}
     * open/close returns {@code null} (no match). An empty ("") open/close
     * returns {@code null} (no match).
     * </p>
     * <p>
     * <pre>
     * StringUtils.substringsBetween("[a][b][c]", "[", "]") = ["a","b","c"]
     * StringUtils.substringsBetween(null, *, *)            = null
     * StringUtils.substringsBetween(*, null, *)            = null
     * StringUtils.substringsBetween(*, *, null)            = null
     * StringUtils.substringsBetween("", "[", "]")          = []
     * </pre>
     *
     * @param str   the String containing the substrings, null returns null, empty
     *              returns empty
     * @param open  the String identifying the start of the substring, empty
     *              returns null
     * @param close the String identifying the end of the substring, empty returns
     *              null
     * @return a String Array of substrings, or {@code null} if no match
     * @since 2.3
     */
    public String[] substrsBetween(final String str, final String open,
                                   final String close) {
        return StringUtils.substringsBetween(str, open, close);
    }

    /**
     * <p>
     * Repeat a String {@code repeat} times to form a new String, with a String
     * separator injected each time.
     * </p>
     * <p>
     * <pre>
     * StringUtils.repeat(null, null, 2) = null
     * StringUtils.repeat(null, "x", 2)  = null
     * StringUtils.repeat("", null, 0)   = ""
     * StringUtils.repeat("", "", 2)     = ""
     * StringUtils.repeat("", "x", 3)    = "xxx"
     * StringUtils.repeat("?", ", ", 3)  = "?, ?, ?"
     * </pre>
     *
     * @param str       the String to repeat, may be null
     * @param separator the String to inject, may be null
     * @param repeat    number of times to repeat str, negative treated as zero
     * @return a new String consisting of the original String repeated,
     * {@code null} if null String input
     * @since 2.5
     */
    public String repeat(final String str, final String separator,
                         final int repeat) {
        return StringUtils.repeat(str, separator, repeat);
    }

    /**
     * <p>
     * Repeat a String {@code repeat} times to form a new String.
     * </p>
     * <p>
     * <pre>
     * StringUtils.repeat(null, 2) = null
     * StringUtils.repeat("", 0)   = ""
     * StringUtils.repeat("", 2)   = ""
     * StringUtils.repeat("a", 3)  = "aaa"
     * StringUtils.repeat("ab", 2) = "abab"
     * StringUtils.repeat("a", -2) = ""
     * </pre>
     *
     * @param str    the String to repeat, may be null
     * @param repeat number of times to repeat str, negative treated as zero
     * @return a new String consisting of the original String repeated,
     * {@code null} if null String input
     */
    public String repeat(final String str, final int repeat) {
        return StringUtils.repeat(str, repeat);
    }

    /**
     * @param str
     * @param regexp
     * @return
     */
    public boolean isMatch(String str, String regexp) {
        return Pattern.compile(regexp).matcher(str).find();
    }

    /**
     * @param val
     * @return
     */
    public String concat(Object val) {
        return concat(val, ",");
    }

    /**
     * @param val
     * @param sep
     * @return
     */
    public String concat(Object val, String sep) {
        if (val == null)
            return "";
        if (sep == null)
            sep = ",";
        StringBuilder sb = new StringBuilder();
        if (val instanceof Object[]) {
            Object[] data = (Object[]) val;
            for (int i = 0; i < data.length; i++) {
                sb.append(data[i]);
                if (i < data.length - 1)
                    sb.append(sep);
            }
        } else if (val instanceof Iterable<?>) {
            Iterator<?> itr = ((Iterable<?>) val).iterator();
            while (itr.hasNext()) {
                sb.append(itr.next());
                if (itr.hasNext())
                    sb.append(sep);
            }
        } else
            sb.append(val);
        return sb.toString();
    }

    /**
     * 更新所有的应用中的变量值
     *
     * @param name
     * @param value
     */
    public void applyAll(String name, Object value) {
        if (Character.isDigit(name.charAt(0))) {
            CommandContext cc = scc.getCommandContext();
            for (WorkflowContext wc : cc.getWorkflowContexts())
                wc.applyAll(cc, name, value);
            UserDo user = cc.getUser();
            String orgId = user.getOrg().getId();
            IdentityDao dao = cc.getIdentityDao(false);
            String query = cc.getPlatformConfigOption("dao.userOrgQuery");
            user.setOrg(null);
            user.setOrg(dao.getUserOrg(query, orgId));
            if (user.getDeviceType() != DeviceType.JOB) {
                dao = cc.getIdentityDao(true);
                UserDo tmp = dao.getUser(cc.getProjectService().getConfig()
                        .getAuthenticationQuery(), user.getId());
                user.setExtraInfo(null);
                user.setExtraInfo(tmp.getExtraInfo());
                tmp = null;
            }
        } else//重载其它资源
            scc.getCommandContext().getProjectService().refresh(name);
    }

    /**
     * 生成32位具有唯一的字符串id
     *
     * @return
     */
    public String UUID() {
        String s = UUID.randomUUID().toString();
        return s.substring(0, 8) + s.substring(9, 13) + s.substring(14, 18)
                + s.substring(19, 23) + s.substring(24);
    }

    /**
     * @param count
     * @return
     */
    public String random(final int count) {
        return RandomStringUtils.random(count, false, false);
    }

    public String randomAscii(final int count) {
        return RandomStringUtils.random(count, 32, 127, false, false);
    }

    public String randomAlphabetic(final int count) {
        return RandomStringUtils.random(count, true, false);
    }

    public String randomAlphanumeric(final int count) {
        return RandomStringUtils.random(count, true, true);
    }

    public String randomNumeric(final int count) {
        return RandomStringUtils.random(count, false, true);
    }

    /**
     * @param data
     * @return
     */
    public String encodeBase64(String data) {
        return Base64.encodeBase64String(data.getBytes());
    }

    /**
     * @param data
     * @return
     */
    public byte[] decodeBase64(String data) {
        return Base64.decodeBase64(data);
    }

    /**
     * 转字节码数组成对象
     *
     * @param bytes
     * @return
     */
    public Object toObject(Object bytes) {
        if (!(bytes instanceof byte[]))
            return bytes;
        return SerializationUtils.deserialize((byte[]) bytes);
    }

    /**
     * 执行表达式
     *
     * @param expression
     * @return
     */
    public Object eval(String expression) {
        return RuntimeUtils.eval(scc.getCommandContext(), expression, scc.getTarget(), scc.getScope());
    }

    /**
     * 调用外部rest api
     *
     * @param url
     * @param data
     * @return
     */
    public Object callRestApi(String url, Object data) {
        String content = null, result;
        if (data != null) {
            if (data instanceof String)
                content = ((String) data).trim();
            else
                content = JSON.toJSONString(data);
            return HttpUtil.post(url, content);
        } else {
            return HttpUtil.get(url);
        }
    }
}
